<template>
  <div
    ref="container"
    :class="`md-live layout-${layout}`"
    v-observe-visibility="visibilityChanged"
    v-if="innerCode"
  >
    <div class="md-live-editor">
      <div class="md-live-editor-container">
        <prism-editor
          v-model="innerCode"
          :highlight="highlighter"
          :readonly="readOnly"
        >
        </prism-editor>
      </div>
      <div class="md-live-tag">live</div>
      <code-block-copy-clipboard
        :source="innerCode"
      ></code-block-copy-clipboard>
    </div>
    <div ref="previewContainer" class="md-live-preview"></div>
  </div>
</template>

<script lang="ts">
import { PrismEditor } from 'vue-prism-editor'
import 'vue-prism-editor/dist/prismeditor.min.css'
import { highlight, languages } from 'prismjs/components/prism-core'
import 'prismjs/components/prism-clike'
import 'prismjs/components/prism-javascript'
import 'prism-themes/themes/prism-material-oceanic.css'
import { loadScriptsAsync } from '../helper/loadScripts'
import { addListener, removeListener } from 'resize-detector'
import debounce from 'lodash/debounce'
import {
  defineComponent,
  watch,
  ref,
  unref,
  onMounted,
  onUnmounted
} from '@vue/composition-api'
import * as base64 from 'js-base64'
import { createSandbox } from '../helper/sandbox'
import CodeBlockCopyClipboard from './CodeBlockCopyClipboard.vue'

declare const echarts: any

function ensureECharts(locale) {
  if (typeof echarts === 'undefined') {
    const isCN = locale === 'zh'
    const lib = process.env.NUXT_ENV_DEPLOY === 'asf' ? 'echarts' : 'echarts-nightly'
    return loadScriptsAsync([
      isCN
        ? `https://registry.npmmirror.com/${lib}/latest/files/dist/echarts.min.js`
        : `https://fastly.jsdelivr.net/npm/${lib}@latest/dist/echarts.min.js`
    ]).then(() => {})
  }
  return Promise.resolve()
}

export default defineComponent({
  components: {
    PrismEditor,
    CodeBlockCopyClipboard
  },
  props: {
    lang: {
      type: String,
      default: 'js'
    },

    code: {
      type: String
    },

    layout: {
      type: String,
      default: 'tb',
      validator(value: string) {
        return ['lr', 'tb', 'rl', 'bt'].includes(value)
      }
    },

    height: {
      type: Number
    },

    readOnly: {
      type: Boolean,
      default: false
    }
  },

  setup(props, context) {
    const innerCode = ref(base64.decode(props.code))
    const previewContainer = ref<HTMLElement | null>(null)
    const container = ref<HTMLElement | null>(null)

    let sandbox: ReturnType<typeof createSandbox>

    function update() {
      if (props.height) {
        container.value!.style.height = props.height + 'px'
      }
      ensureECharts((context.root as any).$i18n.locale).then(() => {
        if (!sandbox) {
          addListener(unref(previewContainer)!, resize)
          sandbox = createSandbox()
        }
        // TODO refresh.
        try {
          unref(previewContainer) &&
            sandbox.run(unref(previewContainer)!, unref(innerCode))
        } catch (e) {
          console.error(e)
        }
      })
    }

    function resize() {
      if (sandbox) {
        sandbox.resize()
      }
    }

    const debouncedUpdate = debounce(update, 500, {
      trailing: true
    })

    watch(innerCode, () => {
      debouncedUpdate()
    })

    // onMounted(() => {
    //   debouncedUpdate()
    // })

    onUnmounted(() => {
      removeListener(unref(previewContainer)!, resize)
    })

    return {
      innerCode,
      previewContainer,
      container,
      highlighter(code) {
        return highlight(code, languages[props.lang] || languages.js)
      },
      visibilityChanged(isVisible) {
        if (isVisible) {
          if (!sandbox) {
            debouncedUpdate()
          } else {
            sandbox.resume()
          }
        } else {
          if (sandbox) {
            sandbox.pause()
          }
        }
      }
    }
  }
})
</script>

<style lang="postcss">
.md-live {
  @apply overflow-hidden;
  @apply shadow-lg rounded-lg mt-10 mb-20;
  @apply flex flex-col-reverse;

  @media (max-width: 768px) {
    min-height: 500px;
  }

  @media (min-width: 768px) {
    &.layout-lr {
      @apply flex-row;
    }
    &.layout-rl {
      @apply flex-row-reverse;
    }
    &.layout-tb {
      @apply flex-col;
    }
    &.layout-bt {
      @apply flex-col-reverse;
    }
    &.layout-lr,
    &.layout-rl {
      @apply items-stretch;

      .md-live-editor-container {
        height: 100%;
      }
      .md-live-editor {
        @apply flex-1;
      }
      .md-live-preview {
        @apply flex-1;
        height: auto;
      }
    }
  }
}

/* required class */
.md-live-editor {
  position: relative;

  ::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.3) !important;
  }

  .md-live-editor-container {
    /* we dont use `language-` classes anymore so thats why we need to add background and text color manually */
    background: #263238;

    max-height: 500px;
    overflow-y: auto;

    font-size: 13px;
    padding: 10px;

    @media (max-width: 768px) {
      max-height: 300px;
    }
  }

  pre {
    color: #c3cee3;
  }

  .md-live-tag {
    position: absolute;
    right: 0;
    top: 0;
    text-transform: uppercase;
    @apply mr-7 mt-3;
    color: #f7fafc;
    z-index: 10;
  }

  .clipboard {
    display: none;
  }

  &:hover {
    .clipboard {
      display: block;
    }
  }
}

.md-live-preview {
  height: 300px;
  overflow: hidden;
}

.prism-editor-wrapper .prism-editor__editor,
.prism-editor-wrapper .prism-editor__textarea {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    'Liberation Mono', 'Courier New', monospace;
  line-height: 1.5;
}

/* optional class for removing the outline */
.prism-editor__textarea:focus {
  outline: none;
}
</style>
